#!/usr/bin/env bash
set -eo pipefail

# NOTE: if you make modifications to this script, please increment the version number.
# Major / minor: incremented for each stable release of Foundry.
# Patch: incremented for each change between stable releases.
FOUNDRYUP_INSTALLER_VERSION="1.0.1"

BASE_DIR=${XDG_CONFIG_HOME:-$HOME}
FOUNDRY_DIR=${FOUNDRY_DIR:-"$BASE_DIR/.foundry"}
FOUNDRY_VERSIONS_DIR="$FOUNDRY_DIR/versions"
FOUNDRY_BIN_DIR="$FOUNDRY_DIR/bin"
FOUNDRY_MAN_DIR="$FOUNDRY_DIR/share/man/man1"
FOUNDRY_BIN_URL="https://raw.gitmirror.com/foundry-rs/foundry/master/foundryup/foundryup"
FOUNDRY_BIN_PATH="$FOUNDRY_BIN_DIR/foundryup"

FOUNDRYUP_JOBS=""

BINS=(forge cast anvil chisel)

export RUSTFLAGS="${RUSTFLAGS:--C target-cpu=native}"

main() {
  need_cmd git
  need_cmd curl

  while [[ -n $1 ]]; do
    case $1 in
      --)               shift; break;;

      -v|--version)     shift; version;;
      -U|--update)      shift; update;;
      -r|--repo)        shift; FOUNDRYUP_REPO=$1;;
      -b|--branch)      shift; FOUNDRYUP_BRANCH=$1;;
      -i|--install)     shift; FOUNDRYUP_VERSION=$1;;
      -l|--list)        shift; list;;
      -u|--use)         shift; FOUNDRYUP_VERSION=$1; use;;
      -p|--path)        shift; FOUNDRYUP_LOCAL_REPO=$1;;
      -P|--pr)          shift; FOUNDRYUP_PR=$1;;
      -C|--commit)      shift; FOUNDRYUP_COMMIT=$1;;
      -j|--jobs)        shift; FOUNDRYUP_JOBS=$1;;
      --arch)           shift; FOUNDRYUP_ARCH=$1;;
      --platform)       shift; FOUNDRYUP_PLATFORM=$1;;
      -h|--help)
        usage
        exit 0
        ;;
      *)
        warn "unknown option: $1"
        usage
        exit 1
    esac; shift
  done

  CARGO_BUILD_ARGS=(--release)

  if [ -n "$FOUNDRYUP_JOBS" ]; then
    CARGO_BUILD_ARGS+=(--jobs "$FOUNDRYUP_JOBS")
  fi

  # Print the banner after successfully parsing args
  banner

  if [ -n "$FOUNDRYUP_PR" ]; then
    if [ -z "$FOUNDRYUP_BRANCH" ]; then
      FOUNDRYUP_BRANCH="refs/pull/$FOUNDRYUP_PR/head"
    else
      err "can't use --pr and --branch at the same time"
    fi
  fi

  check_bins_in_use

  # Installs foundry from a local repository if --path parameter is provided
  if [[ -n "$FOUNDRYUP_LOCAL_REPO" ]]; then
    need_cmd cargo

    # Ignore branches/versions as we do not want to modify local git state
    if [ -n "$FOUNDRYUP_REPO" ] || [ -n "$FOUNDRYUP_BRANCH" ] || [ -n "$FOUNDRYUP_VERSION" ]; then
      warn "--branch, --install, --use, and --repo arguments are ignored during local install"
    fi

    # Enter local repo and build
    say "installing from $FOUNDRYUP_LOCAL_REPO"
    cd "$FOUNDRYUP_LOCAL_REPO"
    ensure cargo build --bins "${CARGO_BUILD_ARGS[@]}"

    for bin in "${BINS[@]}"; do
      # Remove prior installations if they exist
      rm -f "$FOUNDRY_BIN_DIR/$bin"
      # Symlink from local repo binaries to bin dir
      ensure ln -s "$PWD/target/release/$bin" "$FOUNDRY_BIN_DIR/$bin"
    done

    say "done"
    exit 0
  fi

  FOUNDRYUP_REPO=${FOUNDRYUP_REPO:-foundry-rs/foundry}

  # Install by downloading binaries
  if [[ "$FOUNDRYUP_REPO" == "foundry-rs/foundry" && -z "$FOUNDRYUP_BRANCH" && -z "$FOUNDRYUP_COMMIT" ]]; then
    FOUNDRYUP_VERSION=${FOUNDRYUP_VERSION:-stable}
    FOUNDRYUP_TAG=$FOUNDRYUP_VERSION

    # Normalize versions (handle channels, versions without v prefix
    if [[ "$FOUNDRYUP_VERSION" =~ ^nightly ]]; then
      FOUNDRYUP_VERSION="nightly"
    elif [[ "$FOUNDRYUP_VERSION" == [[:digit:]]* ]]; then
      # Add v prefix
      FOUNDRYUP_VERSION="v${FOUNDRYUP_VERSION}"
      FOUNDRYUP_TAG="${FOUNDRYUP_VERSION}"
    fi

    say "installing foundry (version ${FOUNDRYUP_VERSION}, tag ${FOUNDRYUP_TAG})"

    uname_s=$(uname -s)
    PLATFORM=$(tolower "${FOUNDRYUP_PLATFORM:-$uname_s}")
    EXT="tar.gz"
    case $PLATFORM in
      linux) ;;
      darwin|mac*)
        PLATFORM="darwin"
        ;;
      mingw*|win*)
        EXT="zip"
        PLATFORM="win32"
        ;;
      *)
        err "unsupported platform: $PLATFORM"
        ;;
    esac

    uname_m=$(uname -m)
    ARCHITECTURE=$(tolower "${FOUNDRYUP_ARCH:-$uname_m}")
    if [ "${ARCHITECTURE}" = "x86_64" ]; then
      # Redirect stderr to /dev/null to avoid printing errors if non Rosetta.
      if [ "$(sysctl -n sysctl.proc_translated 2>/dev/null)" = "1" ]; then
        ARCHITECTURE="arm64" # Rosetta.
      else
        ARCHITECTURE="amd64" # Intel.
      fi
    elif [ "${ARCHITECTURE}" = "arm64" ] ||[ "${ARCHITECTURE}" = "aarch64" ] ; then
      ARCHITECTURE="arm64" # Arm.
    else
      ARCHITECTURE="amd64" # Amd.
    fi

    # Compute the URL of the release tarball in the Foundry repository.
    RELEASE_URL="https://hub.gitmirror.com/https://github.com/${FOUNDRYUP_REPO}/releases/download/${FOUNDRYUP_TAG}/"
    BIN_ARCHIVE_URL="${RELEASE_URL}foundry_${FOUNDRYUP_VERSION}_${PLATFORM}_${ARCHITECTURE}.$EXT"
    MAN_TARBALL_URL="${RELEASE_URL}foundry_man_${FOUNDRYUP_VERSION}.tar.gz"

    ensure mkdir -p "$FOUNDRY_VERSIONS_DIR"
    # Download and extract the binaries archive
    say "downloading forge, cast, anvil, and chisel for $FOUNDRYUP_TAG version"
    if [ "$PLATFORM" = "win32" ]; then
      tmp="$(mktemp -d 2>/dev/null || echo ".")/foundry.zip"
      ensure download "$BIN_ARCHIVE_URL" "$tmp"
      ensure unzip "$tmp" -d "$FOUNDRY_VERSIONS_DIR/$FOUNDRYUP_TAG"
      rm -f "$tmp"
    else
      tmp="$(mktemp -d 2>/dev/null || echo ".")/foundry.tar.gz"
      ensure download "$BIN_ARCHIVE_URL" "$tmp"
      # Make sure it's a valid tar archive.
      ensure tar tf $tmp 1> /dev/null
      ensure mkdir -p "$FOUNDRY_VERSIONS_DIR/$FOUNDRYUP_TAG"
      ensure tar -C "$FOUNDRY_VERSIONS_DIR/$FOUNDRYUP_TAG" -xvf $tmp
      rm -f "$tmp"
    fi

    # Optionally download the manuals
    if check_cmd tar; then
      say "downloading manpages"
      mkdir -p "$FOUNDRY_MAN_DIR"
      download "$MAN_TARBALL_URL" | tar -xzC "$FOUNDRY_MAN_DIR"
    else
      say 'skipping manpage download: missing "tar"'
    fi

    # Use newly installed version.
    FOUNDRYUP_VERSION=$FOUNDRYUP_TAG
    use

    say "done!"

  # Install by cloning the repo with the provided branch/tag
  else
    need_cmd cargo
    FOUNDRYUP_BRANCH=${FOUNDRYUP_BRANCH:-master}
    REPO_PATH="$FOUNDRY_DIR/$FOUNDRYUP_REPO"
    AUTHOR="$(echo "$FOUNDRYUP_REPO" | cut -d'/' -f1 -)"

    # If repo path does not exist, grab the author from the repo, make a directory in .foundry, cd to it and clone.
    if [ ! -d "$REPO_PATH" ]; then
      ensure mkdir -p "$FOUNDRY_DIR/$AUTHOR"
      cd "$FOUNDRY_DIR/$AUTHOR"
      ensure git clone "https://gitee.com/TriFive/foundry.git"
    fi

    # Force checkout, discarding any local changes
    cd "$REPO_PATH"
    ensure git fetch origin "${FOUNDRYUP_BRANCH}:remotes/origin/${FOUNDRYUP_BRANCH}"
    ensure git checkout "origin/${FOUNDRYUP_BRANCH}"

    # Create custom version based on the install method, e.g.:
    # - foundry-rs-commit-c22c4cc96b0535cd989ee94b79da1b19d236b8db
    # - foundry-rs-pr-1
    # - foundry-rs-branch-chore-bump-forge-std
    if [ -n "$FOUNDRYUP_COMMIT" ]; then
      # If set, checkout specific commit from branch
      ensure git checkout "$FOUNDRYUP_COMMIT"
      FOUNDRYUP_VERSION=$AUTHOR-commit-$FOUNDRYUP_COMMIT
    elif [ -n "$FOUNDRYUP_PR" ]; then
     FOUNDRYUP_VERSION=$AUTHOR-pr-$FOUNDRYUP_PR
    else
      if [ -n "$FOUNDRYUP_BRANCH" ]; then
        NORMALIZED_BRANCH="$(echo "$FOUNDRYUP_BRANCH" | tr / -)"
        FOUNDRYUP_VERSION=$AUTHOR-branch-$NORMALIZED_BRANCH
      fi
    fi
    say "installing version $FOUNDRYUP_VERSION"

    # Build the repo.
    ensure cargo build --bins "${CARGO_BUILD_ARGS[@]}"
    # Create foundry custom version directory.
    ensure mkdir -p "$FOUNDRY_VERSIONS_DIR/$FOUNDRYUP_VERSION"
    for bin in "${BINS[@]}"; do
      for try_path in target/release/$bin target/release/$bin.exe; do
        if [ -f "$try_path" ]; then
          mv -f "$try_path" "$FOUNDRY_VERSIONS_DIR/$FOUNDRYUP_VERSION"
        fi
      done
    done

    # Use newly built version.
    use

    # If help2man is installed, use it to add Foundry man pages.
    if check_cmd help2man; then
      for bin in "${BINS[@]}"; do
        help2man -N "$FOUNDRY_BIN_DIR/$bin" > "$FOUNDRY_MAN_DIR/$bin.1"
      done
    fi

    say "done"
  fi
}

usage() {
  cat 1>&2 <<EOF
The installer for Foundry.

Update or revert to a specific Foundry version with ease.

By default, the latest stable version is installed from built binaries.

USAGE:
    foundryup <OPTIONS>

OPTIONS:
    -h, --help      Print help information
    -v, --version   Print the version of foundryup
    -U, --update    Update foundryup to the latest version
    -i, --install   Install a specific version from built binaries
    -l, --list      List versions installed from built binaries
    -u, --use       Use a specific installed version from built binaries
    -b, --branch    Build and install a specific branch
    -P, --pr        Build and install a specific Pull Request
    -C, --commit    Build and install a specific commit
    -r, --repo      Build and install from a remote GitHub repo (uses default branch if no other options are set)
    -p, --path      Build and install a local repository
    -j, --jobs      Number of CPUs to use for building Foundry (default: all CPUs)
    --arch          Install a specific architecture (supports amd64 and arm64)
    --platform      Install a specific platform (supports win32, linux, and darwin)
EOF
}

version() {
  say "$FOUNDRYUP_INSTALLER_VERSION"
  exit 0
}

update() {
  say "updating foundryup..."

  # Download to a temporary file first
  tmp_file="$(mktemp)"
  ensure download "$FOUNDRY_BIN_URL" "$tmp_file"

  # Replace the current foundryup with the downloaded file
  ensure mv "$tmp_file" "$FOUNDRY_BIN_PATH"
  ensure chmod +x "$FOUNDRY_BIN_PATH"

  say "successfully updated foundryup"
  exit 0
}

list() {
  if [ -d "$FOUNDRY_VERSIONS_DIR" ]; then
    for VERSION in $FOUNDRY_VERSIONS_DIR/*; do
      say "${VERSION##*/}"
      for bin in "${BINS[@]}"; do
        bin_path="$VERSION/$bin"
        say "- $(ensure "$bin_path" -V)"
      done
      printf "\n"
    done
  else
    for bin in "${BINS[@]}"; do
      bin_path="$FOUNDRY_BIN_DIR/$bin"
      say "- $(ensure "$bin_path" -V)"
    done
  fi
  exit 0
}

use() {
  [ -z "$FOUNDRYUP_VERSION" ] && err "no version provided"
  FOUNDRY_VERSION_DIR="$FOUNDRY_VERSIONS_DIR/$FOUNDRYUP_VERSION"
  if [ -d "$FOUNDRY_VERSION_DIR" ]; then

    check_bins_in_use

    for bin in "${BINS[@]}"; do
      bin_path="$FOUNDRY_BIN_DIR/$bin"
      cp "$FOUNDRY_VERSION_DIR/$bin" "$bin_path"
      # Print usage msg
      say "use - $(ensure "$bin_path" -V)"

      # Check if the default path of the binary is not in FOUNDRY_BIN_DIR
      which_path="$(command -v "$bin" || true)"
      if [ -n "$which_path" ] && [ "$which_path" != "$bin_path" ]; then
        warn ""
        cat 1>&2 <<EOF
There are multiple binaries with the name '$bin' present in your 'PATH'.
This may be the result of installing '$bin' using another method,
like Cargo or other package managers.
You may need to run 'rm $which_path' or move '$FOUNDRY_BIN_DIR'
in your 'PATH' to allow the newly installed version to take precedence!

EOF
      fi
    done
    exit 0
  else
    err "version $FOUNDRYUP_VERSION not installed"
  fi
}

say() {
  printf "foundryup: %s\n" "$1"
}

warn() {
  say "warning: ${1}" >&2
}

err() {
  say "$1" >&2
  exit 1
}

tolower() {
  echo "$1" | awk '{print tolower($0)}'
}

need_cmd() {
  if ! check_cmd "$1"; then
    err "need '$1' (command not found)"
  fi
}

check_cmd() {
  command -v "$1" &>/dev/null
}

check_bins_in_use() {
  if check_cmd pgrep; then
    for bin in "${BINS[@]}"; do
      if pgrep -x "$bin" >/dev/null; then
        err "Error: '$bin' is currently running. Please stop the process and try again."
      fi
    done
  else
    warn "Make sure no foundry process is running during the install process!"
  fi
}

# Run a command that should never fail. If the command fails execution
# will immediately terminate with an error showing the failing command.
ensure() {
  if ! "$@"; then err "command failed: $*"; fi
}

# Downloads $1 into $2 or stdout
download() {
  if [ -n "$2" ]; then
    # output into $2
    if check_cmd curl; then
      curl -#o "$2" -L "$1"
    else
      wget --show-progress -qO "$2" "$1"
    fi
  else
    # output to stdout
    if check_cmd curl; then
      curl -#L "$1"
    else
      wget --show-progress -qO- "$1"
    fi
  fi
}

# Banner Function for Foundry
banner() {
  printf '

.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx

 ╔═╗ ╔═╗ ╦ ╦ ╔╗╔ ╔╦╗ ╦═╗ ╦ ╦         Portable and modular toolkit
 ╠╣  ║ ║ ║ ║ ║║║  ║║ ╠╦╝ ╚╦╝    for Ethereum Application Development
 ╚   ╚═╝ ╚═╝ ╝╚╝ ═╩╝ ╩╚═  ╩                 written in Rust.

.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx

Repo       : https://github.com/foundry-rs/foundry
Book       : https://book.getfoundry.sh/
Chat       : https://t.me/foundry_rs/
Support    : https://t.me/foundry_support/
Contribute : https://github.com/foundry-rs/foundry/blob/master/CONTRIBUTING.md

.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx

'
}


main "$@"
